﻿// -----------------------------------------------------------------------
// <copyright file="CrudTests.cs">
// Copyright (c) 2012-2014 Andrey Veselov. All rights reserved.
// License:  Microsoft Public License (MS-PL)
// Contacts: http://andrey.moveax.com  andrey@moveax.com
// </copyright>
// -----------------------------------------------------------------------

namespace Moveax.DataAccess.Cqrs.Highway.EF.UnitTests
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Linq.Expressions;
	using Microsoft.VisualStudio.TestTools.UnitTesting;
	using Moq;
	using Moveax.DataAccess.Cqrs.Highway.EF.UnitTests.DataAccess.Database.Dto;
	using Moveax.DataAccess.Cqrs.Highway.EF.UnitTests.Model.Commands.Links;
	using Moveax.DataAccess.Cqrs.Highway.EF.UnitTests.Model.Commands.Users;
	using Moveax.DataAccess.Cqrs.Highway.EF.UnitTests.Model.Queries.Links;
	using Moveax.DataAccess.Cqrs.Highway.EF.UnitTests.Model.Queries.Users;
	using Moveax.DataAccess.Cqrs.Highway.UnitTests.Model;
	using Moveax.DataAccess.Cqrs.Highway.UnitTests.Model.Queries;

	[TestClass]
	public class CrudTests : TestClassBase
	{
		[TestMethod]
		public void EFGetExistingObject()
		{
			// Arrange 
			User profile = null;

			// Act
			using (var unitOfWork = this._unitOfWorkFactory.Create())
				profile = unitOfWork.ResolveQuery<IGetUserByIdQuery>().Execute(this._sampleData.Id, includeLinks: false);

			// Assert
			Assert.IsNotNull(profile);
			Assert.AreEqual(this._sampleData.Id, profile.Id);
			Assert.AreEqual(this._sampleData.Name, profile.Name);
			Assert.AreEqual(this._sampleData.Email, profile.Email);
			Assert.AreEqual(this._sampleData.IsDisabled, profile.IsDisabled);
		}

		[TestMethod]
		public void EFGetNonexistentObject()
		{
			// Arrange 
			User profile = null;

			// Act
			using (var unitOfWork = this._unitOfWorkFactory.Create())
				profile = unitOfWork.ResolveQuery<IGetUserByIdQuery>().Execute(int.MaxValue, includeLinks: false);

			// Assert
			Assert.IsNull(profile);
		}

		[TestMethod]
		public void EFGetObjectIncludingNavigationProperties()
		{
			// Arrange 
			User profile = null;

			// Act
			using (var unitOfWork = this._unitOfWorkFactory.Create())
				profile = unitOfWork.ResolveQuery<IGetUserByIdQuery>().Execute(this._sampleData.Id, includeLinks: true);

			// Assert
			Assert.IsNotNull(profile);
			Assert.AreEqual(this._sampleData.Id, profile.Id);
			Assert.AreEqual(this._sampleData.Name, profile.Name);
			Assert.AreEqual(this._sampleData.Email, profile.Email);
			Assert.AreEqual(this._sampleData.IsDisabled, profile.IsDisabled);

			Assert.IsNotNull(profile.Links);
			Assert.AreEqual(this._sampleData.Links.Count(), profile.Links.Count());

			bool allLinksWasFound = profile.Links
				.Select(l => this._sampleData.Links.Any(e => (e.Id == l.Id)))
				.Aggregate(true, (acc, val) => acc && val);

			Assert.IsTrue(allLinksWasFound);
		}

		[TestMethod]
		public void EFAddObject()
		{
			// Arrange
			const string name = "John Doe";
			const string email = "johndoe@unknownserver.doe";
			const bool isDisabled = false;
			var links = new[] { new Link() { Title = "SomeTitle", Url = @"http://somesite.com/" } };

			User savedData = null;

			var cmd = new CreateUserCommand(name, email, isDisabled, links);

			// Act
			this._unitOfWorkFactory.ExecuteSingleCommand(cmd);

			using (var unitOfWork = this._unitOfWorkFactory.Create())
				savedData = unitOfWork.ResolveQuery<IGetUserByIdQuery>().Execute(cmd.Id, includeLinks: true);

			// Assert
			Assert.IsNotNull(savedData);
			Assert.AreEqual(cmd.Id, savedData.Id);
			Assert.AreEqual(name, savedData.Name);
			Assert.AreEqual(email, savedData.Email);
			Assert.AreEqual(isDisabled, savedData.IsDisabled);

			Assert.IsNotNull(savedData.Links);
			Assert.AreEqual(links.Count(), savedData.Links.Count());

			var link = savedData.Links.First();
			Assert.AreEqual(savedData.Id, link.UserProfileId);
			Assert.AreEqual(links[0].Title, link.Title);
			Assert.AreEqual(links[0].Url, link.Url);
		}

		[TestMethod]
		public void EFRemoveObject()
		{
			// Arrange
			int initialCount = 0;
			var cmd = new RemoveUserCommand(this._sampleData.Id);

			// Act
			initialCount = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetCountQuery, int>(q => q.Execute<UserDto>());

			this._unitOfWorkFactory.ExecuteSingleCommand(cmd);

			int resultCount = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetCountQuery, int>(q => q.Execute<UserDto>());

			// Assert
			Assert.AreEqual(initialCount - 1, resultCount);
		}

		[TestMethod]
		public void EFRemoveMatchedObjects()
		{
			// Arrange
			const int insertedObjectsCount = 10;

			using (var unitOfWork = this._unitOfWorkFactory.Create()) {
				this.InsertObjectsIntoRepository(unitOfWork, insertedObjectsCount);
				unitOfWork.Commit();
			}

			IEnumerable<UserDto> users = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetAllQuery, IEnumerable<UserDto>>(q => q.Execute<UserDto>());

			if (!users.Any(u => u.IsDisabled))
				throw new InvalidOperationException("There are no disabled users in the database.");

			var cmd = new RemoveDisabledUsersCommand();

			// Act
			this._unitOfWorkFactory.ExecuteSingleCommand(cmd);

			users = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetAllQuery, IEnumerable<UserDto>>(q => q.Execute<UserDto>());

			// Assert
			Assert.IsTrue(users.All(u => !u.IsDisabled));
		}

		[TestMethod]
		public void EFUpdateSingleObject()
		{
			// Arrange
			User user = null;
			const string name = "John Doe";
			const string email = "johndoe@unknownserver.doe";
			const bool isDisabled = false;

			// NOTE: handler for this command should read entire profile and update rating value
			var cmd = new UpdateUserCommand(this._sampleData.Id, name, email, isDisabled);

			// Act
			this._unitOfWorkFactory.ExecuteSingleCommand(cmd);

			user = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetUserByIdQuery, User>(q => q.Execute(cmd.Id, includeLinks: false));

			// Assert
			Assert.IsNotNull(user);
			Assert.AreEqual(this._sampleData.Id, user.Id);
			Assert.AreEqual(name, user.Name);
			Assert.AreEqual(email, user.Email);
			Assert.AreEqual(isDisabled, user.IsDisabled);
		}

		[TestMethod]
		public void EFUpdateMultipleObjects()
		{
			// Validate command handler (it must call Update() method)
			var mockedRepository = new Mock<IRepository>();
			var cmdHandler = new DataAccess.CommandHandlers.Users.DisableOldUsersCommandHandler(mockedRepository.Object);
			cmdHandler.Execute(new DisableOldUsersCommand(DateTime.UtcNow));
			mockedRepository.Verify(r => r.Update(
				It.IsAny<UserDto>(),
				It.IsAny<Expression<Func<UserDto, bool>>>(),
				It.IsAny<Expression<Func<UserDto, object>>[]>()),
				Times.AtLeastOnce());

			// Arrange
			const int insertedObjectsCount = 10;

			using (var unitOfWork = this._unitOfWorkFactory.Create()) {
				this.InsertObjectsIntoRepository(unitOfWork, insertedObjectsCount);
				unitOfWork.Commit();
			}

			IEnumerable<UserDto> usersBefore = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetAllQuery, IEnumerable<UserDto>>(q => q.Execute<UserDto>());

			// NOTE: handler for this command should update all entries at once
			var cmd = new DisableOldUsersCommand(DateTime.Now);

			// Act
			this._unitOfWorkFactory.ExecuteSingleCommand(cmd);

			IEnumerable<UserDto> usersAfter = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetAllQuery, IEnumerable<UserDto>>(q => q.Execute<UserDto>());

			// Assert
			Assert.AreEqual(usersBefore.Count(), usersAfter.Count());

			foreach (var user in usersAfter) {
				var userBefore = usersBefore.FirstOrDefault(u => u.Id == user.Id);
				Assert.IsNotNull(userBefore);
				Assert.AreEqual(userBefore.Name, user.Name);
				Assert.AreEqual(userBefore.Email, user.Email);
				Assert.AreEqual(userBefore.Created, user.Created);
				Assert.IsTrue(user.IsDisabled);
			}
		}

		[TestMethod]
		public void EFUpdateSpecifiedProperties()
		{
			// Arrange 
			const string newName = "John Doe";

			// Validate command handler (it must call UpdateSpecifiedProperties() method)
			var mockedRepository = new Mock<IRepository>();
			var cmdHandler = new DataAccess.CommandHandlers.Users.UpdateUserNameCommandHandler(mockedRepository.Object);
			cmdHandler.Execute(new UpdateUserNameCommand(this._sampleData.Id, newName));
			mockedRepository.Verify(r => r.UpdateSpecifiedProperties(
				It.IsAny<UserDto>(),
				It.IsAny<Expression<Func<UserDto, object>>[]>()),
				Times.AtLeastOnce());

			// NOTE: handler for this command should update login only (without reading entire entity)
			var cmd = new UpdateUserNameCommand(this._sampleData.Id, newName);

			// Act
			this._unitOfWorkFactory.ExecuteSingleCommand(cmd);

			User user = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetUserByIdQuery, User>(q => q.Execute(cmd.Id, includeLinks: false));

			// Assert
			Assert.IsNotNull(user);
			Assert.AreEqual(this._sampleData.Id, user.Id);
			Assert.AreEqual(newName, user.Name);
			Assert.AreEqual(this._sampleData.IsDisabled, user.IsDisabled);
		}

		[TestMethod]
		public void EFUpdateExcludeProperties()
		{
			// Arrange
			Link expectedLinkInfo = this._sampleData.Links.First();
			Link resultLinkInfo = null;
			var newTitle = DateTime.Now.ToShortTimeString();

			// Validate command handler (it must call UpdateExcludeProperties() method)
			var mockedRepository = new Mock<IRepository>();
			var cmdHandler = new DataAccess.CommandHandlers.Links.UpdateLinkTitleCommandHandler(mockedRepository.Object);
			cmdHandler.Execute(new UpdateLinkTitleCommand(expectedLinkInfo.Id, newTitle));
			mockedRepository.Verify(r => r.UpdateExcludeProperties(
				It.IsAny<LinkDto>(),
				It.IsAny<Expression<Func<LinkDto, object>>[]>()),
				Times.AtLeastOnce());

			// NOTE: handler for this command should update title only (without reading entire entity)
			var cmd = new UpdateLinkTitleCommand(expectedLinkInfo.Id, newTitle);

			// Act
			this._unitOfWorkFactory.ExecuteSingleCommand(cmd);

			resultLinkInfo = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetLinksByUserIdQuery, IEnumerable<Link>>(q => q.Execute(this._sampleData.Id))
				.FirstOrDefault(l => l.Id == cmd.Id);

			// Assert
			Assert.IsNotNull(resultLinkInfo);
			Assert.AreEqual(expectedLinkInfo.Id, resultLinkInfo.Id);
			Assert.AreEqual(newTitle, resultLinkInfo.Title);
			Assert.AreEqual(expectedLinkInfo.Url, resultLinkInfo.Url);
			Assert.AreEqual(expectedLinkInfo.UserProfileId, resultLinkInfo.UserProfileId);
		}
	}
}